/**
 */
package br.org.bertol.mestrado.engine.optimisation.aco;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.List;
import java.util.ListIterator;

import br.org.bertol.mestrado.engine.Objective;
import br.org.bertol.mestrado.engine.exception.HeuristicsException;
import br.org.bertol.mestrado.engine.optimisation.AbstractMetaHeuristica;
import br.org.bertol.mestrado.engine.optimisation.entity.Ant;
import br.org.bertol.mestrado.engine.optimisation.pso.PsoRepository;

/**
 * @author contaqualquer
 * @param <T>
 *            Tipo de manipulação
 */
public class AntColonyOptimization<T> extends AbstractMetaHeuristica<T, Ant<T>> {

    /**
     * Matriz de feromônios para cada objetivo.
     */
    private final transient EnumMap<Objective, Double[][]>   pheromones;

    /**
     * Taxa de evaporação.
     */
    private final transient float                            fEvaporation;

    /**
     * Taxa mínima de feromônios.
     */
    private final transient float                            fMinPheromone;

    /**
     * Respositório local de formigas.
     */
    private final transient AcoRepository<T>                 acoRepository;

    /***/
    private final transient int                              PLUS_15 = 15;

    /***/
    private final transient int                              PLUS_10 = 10;

    /***/
    private final transient int                              PLUS_5  = 5;

    /***/
    private final transient EnumMap<Objective, Number>       c1MelhorIteracaoSet;

    /***/
    private final transient EnumMap<Objective, ArrayList<T>> s1MelhorIteracaoSet;

    /***/
    private final transient EnumMap<Objective, Number>       c2MelhorIteracaoSet;

    /***/
    private final transient EnumMap<Objective, ArrayList<T>> s2MelhorIteracaoSet;

    /**
     * @param numIter
     *            número de iterações
     * @param ants
     *            lista de formigas
     * @param evaporation
     *            Taxa de evaporação do feromônio
     * @param minPheromone
     *            Valor mínimo de feromônios
     * @param classes
     *            Lista de classes do sistema
     * @param objectives
     *            Lista de objetivos
     */
    public AntColonyOptimization(final long numIter, final List<Ant<T>> ants,
        final float evaporation, final float minPheromone,
        final ArrayList<T> classes, final Objective[] objectives) {

        super(numIter, ants, classes, objectives);

        pheromones = new EnumMap<Objective, Double[][]>(Objective.class);

        for (Objective objective : objectives) {

            final Double[][] pheronome = new Double[classes.
                    size()][classes.size()];

            for (Double[] ds : pheronome) {
                Arrays.fill(ds, 1.0);
            }

            // inicia matriz de feromônios
            pheromones.put(objective, pheronome);

        }

        fEvaporation = evaporation;

        fMinPheromone = minPheromone;

        acoRepository = new AcoRepository<T>();

        c1MelhorIteracaoSet = new EnumMap<Objective, Number>(
                Objective.class);

        s1MelhorIteracaoSet = new EnumMap<Objective, ArrayList<T>>(
                Objective.class);

        c2MelhorIteracaoSet = new EnumMap<Objective, Number>(
                Objective.class);

        s2MelhorIteracaoSet = new EnumMap<Objective, ArrayList<T>>(
                Objective.class);

        clearIterations();
    }

    /**
     * {@inheritDoc}
     * @see br.org.bertol.mestrado.engine.optimisation.Tecninca#evaluate()
     */
    @SuppressWarnings("unchecked")
    @Override
    public final void evaluate() throws HeuristicsException {

        try {

            // mutantes
            final ArrayList<T> startClasses = (ArrayList<T>) classes.clone();

            for (int i = 0; i < numIteracoes; i++) {

                for (final ListIterator<Ant<T>> iterator = listaPareto
                        .listIterator(); iterator.hasNext();) {

                    final Ant<T> ant = iterator.next();

                    // zera dados da formiga
                    ant.reset();

                    final Double startClass = startClasses.size()
                            * Math.random();

                    // formiga constroi o caminho
                    ant.doPath(startClasses.get(startClass.intValue()),
                               classes, pheromones, objectives);

                    final Ant<T> betterAnt = (Ant<T>)
                            ant.optimal(ant).clone();

                    // atualização da matriz de feromônios
                    for (Objective objective : objectives) {

                        // pega matriz de feromônios
                        final Double[][] temp = pheromones.get(objective);

                        // atualiza as posições com base no caminho encontrado

                        for (int pointer = 0; pointer < betterAnt.getRoute()
                                .size() - 1; pointer++) {

                            final int coluna = classes
                                    .indexOf(betterAnt.getRoute().get(pointer));

                            final int linha = classes
                                    .indexOf(betterAnt.getRoute()
                                    .get(pointer + 1));

                            temp[coluna][linha] = ((1 - fEvaporation)
                                    * temp[coluna][linha])
                                    + (fEvaporation * 1.0);

                        }
                    }

                    // calcula melhores iterações
                    // para auxílio na atualização do feromônio
                    updateBestIterations(betterAnt);

                    // atualiza feromônios
                    updateGlobalPheromone();

                    iterator.set(betterAnt);

                }

                // verifica pareto
                acoRepository.addInto(listaPareto);

                // limpa melhores iterações
                clearIterations();

            }
        } catch (CloneNotSupportedException e) {
            throw new HeuristicsException(e);
        } catch (Exception e) {
            throw new HeuristicsException(e);
        }
    }

    /**
     * Realiza atualização global dos feromônios.
     */
    private void updateGlobalPheromone() {

        // atualização global de feromônios

        for (final ListIterator<T> iteratorL = classes
                .listIterator(); iteratorL.hasNext();) {

            final T itemLinha = iteratorL.next();

            final int linha = iteratorL.previousIndex();

            for (final ListIterator<T> iteratorC = classes
                    .listIterator(); iteratorC.hasNext();) {

                final T itemColuna = iteratorC.next();

                final int coluna = iteratorC.previousIndex();

                for (Objective objective : objectives) {

                    final ArrayList<T> tlist1 = s1MelhorIteracaoSet
                            .get(objective);

                    final ArrayList<T> tlist2 = s1MelhorIteracaoSet
                            .get(objective);

                    final Double[][] tmp = pheromones
                            .get(objective);

                    tmp[linha][coluna] *= (1 - fEvaporation);

                    if (tmp[linha][coluna] < fMinPheromone) {
                        tmp[linha][coluna] = Double
                                .valueOf(fMinPheromone);
                    }
                    if (tlist1.contains(itemLinha)
                            && tlist1.indexOf(itemLinha) < tlist1.size() - 1
                            && tlist1.get(tlist1.indexOf(itemLinha) + 1)
                            .equals(itemColuna)) {
                        tmp[linha][coluna] += (PLUS_10 * fEvaporation);
                    }
                    if (tlist2.contains(itemLinha)
                            && tlist2.indexOf(itemLinha) < tlist2.size() - 1
                            && tlist2.get(tlist2.indexOf(itemLinha) + 1)
                            .equals(itemColuna)) {
                        tmp[linha][coluna] += (PLUS_5 * fEvaporation);
                    }
                }

            }
        }

    }

    /**
     * Atualiza as melhores iterações de cada objetivo.
     * @param ant
     *            Formiga
     */
    @SuppressWarnings("unchecked")
    private void updateBestIterations(final Ant<T> ant) {
        for (final Objective objective : objectives) {

            if (ant.getHeight().get(objective).intValue() < c1MelhorIteracaoSet
                    .get(objective).intValue()) {

                // atualiza pesos
                c1MelhorIteracaoSet.put(objective, ant
                        .getHeight().get(objective).intValue());

                c2MelhorIteracaoSet.put(objective,
                                        c1MelhorIteracaoSet
                        .get(objective)
                        .intValue());

                // atualiza caminhos
                s1MelhorIteracaoSet
                        .put(objective,
                             (ArrayList<T>) ant
                        .getRoute().clone());

                s2MelhorIteracaoSet
                        .put(
                             objective,
                             (ArrayList<T>) s1MelhorIteracaoSet
                        .get(objective).clone());
            } else if (ant.getHeight().get(objective)
                    .intValue() < c2MelhorIteracaoSet
                    .get(objective).intValue()) {

                // atualiza caminhos
                s2MelhorIteracaoSet
                        .put(objective,
                             (ArrayList<T>) ant
                        .getRoute().clone());

                // atualiza pesos
                c2MelhorIteracaoSet.put(objective, ant
                        .getHeight().get(objective).intValue());

            }

        }
    }

    /**
     * Realiza start das melhores iterações.
     */
    private void clearIterations() {

        for (final Objective objective : objectives) {

            c1MelhorIteracaoSet.put(objective, Integer.MAX_VALUE);

            s1MelhorIteracaoSet.put(objective, new ArrayList<T>());

            c2MelhorIteracaoSet.put(objective, Integer.MAX_VALUE);

            s2MelhorIteracaoSet.put(objective, new ArrayList<T>());

        }
    }

    /**
     * Retorna o swarm.
     * @return {@link List}
     */
    public final List<Ant<T>> getSwarm() {
        return listaPareto;
    }

    /**
     * @see PsoRepository#getParetoBorder()
     * @return Lista de soluções não dominadas
     */
    public final List<Ant<T>> getParetoBorder() {
        return acoRepository.getParetoBorder();
    }
}
